﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LuceneSearcher;
using Snap.Model;

namespace MarketDataAnalyzer
{
    public class MarketDataAnalyzer
    {
        public const double EstOffsetFromUtc = -5.0;

        public static IEnumerable<PriceAnalysis> Analyze(IEnumerable<SearchResult> searchResults)
        {
            var context = new SnapModelContainer();

            // Get a reference mapping of relevant newsIds to the NewsItem
            var initialNewsIds = searchResults.Select( sr => sr.NewsId ).ToList();
            var idToInitialNewsItem = context.NewsItems
                .Where( ni => initialNewsIds.Contains( ni.Id ) )
                .ToDictionary( ni => ni.Id, ni => ni );

            var matchingNewsIds = searchResults.Select(sr => sr.BestMatchNewsId);
            
            var idToMatchingNewsItem = context.NewsItems
                .Where( ni => matchingNewsIds.Contains( ni.Id ) )
                .ToDictionary( ni => ni.Id, ni => ni );

            // Now, figure out the stock price at the time of the news through 60m before/after
            var priceAnalyses = new List<PriceAnalysis>();
            foreach (var searchResult in searchResults)
            {
                var priceAnalysis = new PriceAnalysis();
                var inputNewsItem = idToInitialNewsItem[searchResult.NewsId];
                var eventTimeInEST = inputNewsItem.PublicationDate.AddHours( EstOffsetFromUtc );
                var inputNewsSymbol = context.NewsToCompanies
                    .Where( ntc => ntc.NewsItemId == inputNewsItem.Id )
                    .Select(ntc => ntc.CompanySymbol).First();
                priceAnalysis.NewsId = inputNewsItem.Id;
                priceAnalysis.NewsEventTimeEst = eventTimeInEST;
                priceAnalysis.Symbol = inputNewsSymbol;
                priceAnalysis.ActualPricePoints = GetAllNearbyPrices( eventTimeInEST, inputNewsSymbol, 60 );
                priceAnalysis.MatchingNewsId = searchResult.BestMatchNewsId;
                
                var matchingNewsItem = idToMatchingNewsItem[searchResult.BestMatchNewsId];
                var timeOfMatchingArticleEST = matchingNewsItem.PublicationDate.AddHours( EstOffsetFromUtc );
                var matchingNewsSymbols = matchingNewsItem.NewsToCompanies.Select( x => x.CompanySymbol );
                foreach (var matchingNewsSymbol in matchingNewsSymbols)
                {
                    priceAnalysis.ProjectedPriceAnalyses.Add( new ProjectedPriceAnalysis
                                                                  {
                                                                      NewsEventTimeEst = timeOfMatchingArticleEST,
                                                                      NewsId = matchingNewsItem.Id,
                                                                      PricePoints =
                                                                          GetAllNearbyPrices( timeOfMatchingArticleEST,
                                                                                              matchingNewsSymbol, 60 )
                                                                  } );
                }
                priceAnalyses.Add( priceAnalysis );
            }
            
            return (priceAnalyses);
        }


        private static IEnumerable<PricePoint> GetAllNearbyPrices(DateTime dateTime, string symbol, long distanceInMinutes)
        {
            const long ticksPerMinute = 600000000;
            long distanceInTicks = ticksPerMinute * distanceInMinutes;
            var context = new SnapModelContainer();
           
            var ticks = dateTime.Ticks;
            var beginningTick = ticks - distanceInTicks;
            var endingTick = ticks + distanceInTicks;
            var anonPricePoints = context.Tickers
                .Where( t => t.CompanySymbol == symbol
                             && t.Tick >= beginningTick
                             && t.Tick <= endingTick )
                .Select( t => new {Price = t.BuyPriceAvg, t.Tick} )
                .ToList();

            var pricePoints = anonPricePoints.Select( app => new PricePoint
                                                                 {
                                                                     Price = app.Price,
                                                                     Time = new DateTime( app.Tick )
                                                                 } ).ToList();

           return (pricePoints);
        }

        public static string GetStandardPredictions( PriceAnalysis analysis )
        {
            var sb = new StringBuilder();
            sb.AppendFormat( "News with Id [{0}] most closely matched NewsItem with Id [{1}]{2}",
                             analysis.NewsId, analysis.MatchingNewsId, Environment.NewLine );
            sb.AppendLine( "Predicted change:");
            sb.AppendFormat( "\t1m: {0}%, 3m: {1}%, 5m: {2}%, 10m: {3}%, 20m: {4}%, 45m: {5}%{6}",
                             analysis.GetProjectedChangeInPercent( 1 ),
                             analysis.GetProjectedChangeInPercent( 3 ),
                             analysis.GetProjectedChangeInPercent( 5 ),
                             analysis.GetProjectedChangeInPercent( 10 ),
                             analysis.GetProjectedChangeInPercent( 20 ),
                             analysis.GetProjectedChangeInPercent( 45 ),
                             Environment.NewLine
                );
            sb.AppendLine( "Actual change:" );
            sb.AppendFormat("\t1m: {0}%, 3m: {1}%, 5m: {2}%, 10m: {3}%, 20m: {4}%, 45m: {5}%{6}",
                             analysis.GetActualChangeInPercentAfterEvent(1),
                             analysis.GetActualChangeInPercentAfterEvent(3),
                             analysis.GetActualChangeInPercentAfterEvent(5),
                             analysis.GetActualChangeInPercentAfterEvent(10),
                             analysis.GetActualChangeInPercentAfterEvent(20),
                             analysis.GetActualChangeInPercentAfterEvent(45),
                             Environment.NewLine
                );
            sb.AppendLine();

            return sb.ToString();
        }

        public static string GetStandardPredictionsCsv( PriceAnalysis priceAnalysis )
        {
            var sb = new StringBuilder();
            // NewsId, MatchingNewsId
            sb.AppendFormat( "{0},{1},", priceAnalysis.NewsId, priceAnalysis.MatchingNewsId );

            // Price at event
            sb.AppendFormat( "{0},", priceAnalysis.GetActualPriceAtRelativeTime( 0 ) );

            // predicted change at 1m, 3m, 5m, 10m, 20m, 45m
            sb.AppendFormat( "{0},{1},{2},{3},{4},{5},",
                             priceAnalysis.GetProjectedChangeInPercent( 1 ),
                             priceAnalysis.GetProjectedChangeInPercent( 3 ),
                             priceAnalysis.GetProjectedChangeInPercent( 5 ),
                             priceAnalysis.GetProjectedChangeInPercent( 10 ),
                             priceAnalysis.GetProjectedChangeInPercent( 20 ),
                             priceAnalysis.GetProjectedChangeInPercent( 45 ) );
            
            // actual change at 1m, 3m, 5m, 10m, 20m, 45m
            sb.AppendFormat( "{0},{1},{2},{3},{4},{5}",
                             priceAnalysis.GetActualChangeInPercentAfterEvent( 1 ),
                             priceAnalysis.GetActualChangeInPercentAfterEvent( 3 ),
                             priceAnalysis.GetActualChangeInPercentAfterEvent( 5 ),
                             priceAnalysis.GetActualChangeInPercentAfterEvent( 10 ),
                             priceAnalysis.GetActualChangeInPercentAfterEvent( 20 ),
                             priceAnalysis.GetActualChangeInPercentAfterEvent( 45 ) );
            sb.Append( Environment.NewLine );
            return sb.ToString();
        }
    }
}
